/*
   Copyright (C) 2022 Ivan Polyakov

   This file is part of fltube.

   fltube is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   fltube is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include "invidious.h"
#include "gettext.h"

typedef struct {
    char *buff;
    size_t buffsz;    /* memory size */
    size_t bufflen;   /* strlen */
    invid_rescb_t cb;
} curl_userdata_t;

struct _invidious_client_struct { /* invid_t */
    char *host;
    curl_userdata_t *data;
};

static inviderr_t curl_userdata_init(curl_userdata_t **data);

static void curl_userdata_cleanup(curl_userdata_t *data);

static size_t write_req_data(void *contents, size_t sz, size_t nmemb,
                             void *userdata);

static int mkreq(curl_userdata_t *data, const char *url);

static void process_res(invid_t client, void *userdata);


inviderr_t invid_init(invid_t *client, const char *host)
{
    *client = (invid_t) malloc(sizeof(invid_t));
    if (!*client) {
        return inviderr_alloc;
    }

    (*client)->host = NULL;
    (*client)->data = NULL;

    if (curl_userdata_init(&(*client)->data)) {
        invid_cleanup(*client);
        return inviderr_alloc;
    }

    invid_set_host(*client, host);
    curl_global_init(CURL_GLOBAL_ALL);

    return inviderr_ok;
}

void invid_cleanup(invid_t client)
{
    curl_global_cleanup();

    if (client->host) {
        free(client->host);
    }

    if (client->data) {
        curl_userdata_cleanup(client->data);
    }

    free(client);
}

inviderr_t invid_set_host(invid_t client, const char *host)
{
    char *newhost;
    size_t hostlen;

    if (client->host) {
        free(client->host);
        client->host = NULL;
    }

    if (!host) {
        return inviderr_ok;
    }

    hostlen = strlen(host);
    newhost = (char*) malloc((hostlen + 1) * sizeof(char));
    newhost[hostlen] = '\0';
    strcpy(newhost, host);
    client->host = newhost;

    return inviderr_ok;
}

int invid_getpop(invid_t client, invid_rescb_t cb, void *userdata)
{
    int status = inviderr_ok;
    char *urlstr;

    CURLU *url = curl_url();
    if ((status = curl_url_set(url, CURLUPART_URL, client->host, 0))) {
        return status;
    }

    status = curl_url_set(url, CURLUPART_PATH, N_("/api/v1/popular"), 0);
    if (status) {
        return status;
    }

    curl_url_get(url, CURLUPART_URL, &urlstr, 0);

    curl_url_cleanup(url);
    client->data->cb = cb;
    status = mkreq(client->data, urlstr);
    if (!status && client->data->cb) {
        process_res(client, userdata);
    }

    return status;
}

const char *invid_strerr(inviderr_t err)
{
    switch (err) {
    case inviderr_ok:
        return _("No errors.");
    case inviderr_alloc:
        return _("Failed to allocate memory!");
    case inviderr_curlinit:
        return _("Failed to initialize cURL!");
    default:
        return _("This is not an Invidious error. "
                 "Try checking for a cURL error");
    }
}


static inviderr_t curl_userdata_init(curl_userdata_t **data)
{
    *data = (curl_userdata_t*) malloc(sizeof(curl_userdata_t));
    if (!*data) {
        return inviderr_alloc;
    }

    (*data)->buff = NULL;
    (*data)->buffsz = (*data)->bufflen = 0;

    return inviderr_ok;
}

static void curl_userdata_cleanup(curl_userdata_t *data)
{
    if (data->buff) {
        free(&data->buff);
    }

    free(data);
}

static size_t write_req_data(void *contents, size_t membsz, size_t nmemb,
                             void *userdata)
{
    size_t sz, realsize;
    curl_userdata_t *data;

    sz = membsz * nmemb;
    data = (curl_userdata_t*) userdata;
    realsize = data->bufflen + sz + 1;

    if (data->buffsz < realsize) {
        char *ptr = (char*) realloc(data->buff, realsize);
        if (!ptr) {
            /* out of memory */
            return 0;
        }
        data->buff = ptr;
        data->buffsz += sz;
    }

    memcpy(&(data->buff[data->bufflen]), contents, sz);
    data->bufflen += sz;
    data->buff[data->bufflen] = '\0';

    return sz;
}

static int mkreq(curl_userdata_t *data, const char *url)
{
    CURL *handle = NULL;
    CURLcode status = CURLE_OK;

    handle = curl_easy_init();
    if (!handle) {
        return inviderr_curlinit;
    }

    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_req_data);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, data);

    status = curl_easy_perform(handle);
    if (status) {
        return status;
    }

    curl_easy_cleanup(handle);
    return CURLE_OK;
}

static void process_res(invid_t client, void *userdata)
{
    cJSON *json = 0;
    int i = 0, j = client->data->bufflen;

    json = cJSON_Parse(client->data->buff);
    while (i < j) {
        client->data->buff[i++] = client->data->buff[j--] = '\0';
    }

    client->data->bufflen = 0;
    client->data->cb(json, userdata);
}

